#include "net/SockOpt.h"
#include "Base/Logger.h"
#include "Base/CurrectThread.h"
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/tcp.h>

namespace toym
{
namespace net
{

int SockOpt::createSocketUnblocked4() {
    int sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        CurrectThread::saveErrno(errno);
        LOG_ERROR << "SockOpt::createSocketUnblocked4 fd "<< sockfd
                  << ", socket fail";
        ::abort();
        CurrectThread::saveErrno(0);
    }
    int ret = SockOpt::setUnblocked(sockfd);
    if (ret < 0) 
        ::abort();
    return sockfd;
}

int SockOpt::bind(int sockfd, const struct sockaddr_in* paddr) {
    int ret = ::bind(sockfd, reinterpret_cast<const struct sockaddr*>(paddr), sizeof(*paddr));
    if (ret < 0) {
        CurrectThread::saveErrno(errno);
        LOG_ERROR << "SockOpt::bind fd "<< sockfd
                  << ", bind fail";
        CurrectThread::saveErrno(0);
    }
    return ret;
}

int SockOpt::listen(int sockfd, int nlisten) {
    int ret = ::listen(sockfd, nlisten);
    if (ret < 0 ){
        CurrectThread::saveErrno(errno);
        LOG_ERROR << "SockOpt::listen fd "<< sockfd
                  << ", listen fail";
        CurrectThread::saveErrno(0);
    }
    return ret;
}

int SockOpt::accept(int sockfd, struct sockaddr_in* paddr) {
    socklen_t len = sizeof(*paddr);
    int ret = ::accept(sockfd, reinterpret_cast<struct sockaddr*>(paddr), &len);
    if (ret < 0) {
        CurrectThread::saveErrno(errno);
        LOG_ERROR << "SockOpt::accept fd "<< sockfd
                  << ", accept fail";
        CurrectThread::saveErrno(0);
    }
    return ret;
}

int SockOpt::accept4(int sockfd, struct sockaddr_in* paddr) {
    socklen_t len = sizeof(*paddr);
    int ret = ::accept4(sockfd, reinterpret_cast<struct sockaddr*>(paddr), &len, SOCK_CLOEXEC | SOCK_NONBLOCK);
    if (ret < 0) {
        CurrectThread::saveErrno(errno);
        LOG_ERROR << "SockOpt::accept fd "<< sockfd
                  << ", accept fail";
        CurrectThread::saveErrno(0);
    }
    return ret;
}

int SockOpt::connect(int sockfd, const struct sockaddr_in* paddr) {
    int ret = ::connect(sockfd, reinterpret_cast<const sockaddr*>(paddr), sizeof(*paddr));
    if (ret < 0) {
        CurrectThread::saveErrno(errno);
        LOG_ERROR << "SockOpt::connect fd "<< sockfd
                  << ", connect fail";
        CurrectThread::saveErrno(0);
    }
    return ret;
}


int SockOpt::shutdownWrite(int sockfd) {
    int ret = ::shutdown(sockfd, SHUT_WR);
    if (ret < 0){
        CurrectThread::saveErrno(errno);
        LOG_ERROR << "SockOpt::shutdownWrite fd "<< sockfd
                  << ", shutdown fail";
        CurrectThread::saveErrno(0);
    }
    return ret;
}

int SockOpt::setUnblocked(int sockfd) {
    int flags = 0;
    flags = ::fcntl(sockfd, F_GETFL);
    if (flags < 0) {
        CurrectThread::saveErrno(errno);
        LOG_ERROR << "SockOpt::setUnblocked fd "<< sockfd
                  << ", fcntl get flags fail";
        CurrectThread::saveErrno(0);
        return flags;
    }
    flags = ::fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
    if (flags < 0) {
        CurrectThread::saveErrno(errno);
        LOG_ERROR << "SockOpt::setUnblocked fd "<< sockfd
                  << ", fcntl set flags fail";
        CurrectThread::saveErrno(0);
    }
    return flags;
}

int SockOpt::getPeerAddr(int sockfd, struct sockaddr_in* paddr) {
    socklen_t len = sizeof(*paddr);
    int ret = ::getpeername(sockfd, reinterpret_cast<struct sockaddr*>(paddr), &len);
    if(ret < 0) {
        CurrectThread::saveErrno(errno);
        LOG_ERROR << "SockOpt::getPeerAddr fd " << sockfd
                  << ", getpeername fail";
        CurrectThread::saveErrno(0);
    }
    return ret;
}

int SockOpt::getLocalAddr(int sockfd, struct sockaddr_in* paddr) {
    socklen_t len = sizeof(*paddr);
    int ret = ::getsockname(sockfd, reinterpret_cast<struct sockaddr*>(paddr), &len);
    if (ret < 0) {
        CurrectThread::saveErrno(errno);
        LOG_ERROR << "SockOpt::getLocalAddr fd " << sockfd
                  << ", getsockname fail";
        CurrectThread::saveErrno(0);
    }
    return ret;
}

int SockOpt::setSocketReuse(int sockfd, bool reuse) {
    int val = (reuse ? 1 : 0);
    int ret = ::setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
    if (ret < 0) {
        CurrectThread::saveErrno(errno);
        LOG_ERROR << "SockOpt::setSocketReuse fd " << sockfd
                  << ", setsockopt set reuseaddr fail";
        CurrectThread::saveErrno(0);
    }
    return ret;
}

int SockOpt::setTcpNoDelay(int sockfd, bool on) {
    int val = (on ? 1 : 0);
    int ret = ::setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val));
    if (ret < 0) {
        CurrectThread::saveErrno(errno);
        LOG_ERROR << "SockOpt::setTcpNoDelay fd " << sockfd
                  << ", setsockopt set TCP_NODELAY fail";
        CurrectThread::saveErrno(0);
    }
    return ret;
}

int SockOpt::setKeepAlive(int sockfd, bool on) {
    int val = (on ? 1 : 0);
    int ret = ::setsockopt(sockfd, IPPROTO_TCP, SO_KEEPALIVE, &val, sizeof(val));
    if (ret < 0) {
        CurrectThread::saveErrno(errno);
        LOG_ERROR << "SockOpt::setKeepAlive fd " << sockfd
                  << ", setsockopt set SO_KEEPALIVE fail";
        CurrectThread::saveErrno(0);
    }
    return ret;
}

int SockOpt::getSocketError(int sockfd) {
    int optval;
    socklen_t optlen = static_cast<socklen_t>(sizeof (optval));
    if (::getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &optval, &optlen) < 0) {
        return errno;
    } else {
        return optval;
    }
}

bool SockOpt::isSelfConnect(int sockfd) {
    struct sockaddr_in addr_local, addr_peer;
    socklen_t slen = sizeof(addr_local);
    bzero(&addr_local, sizeof(addr_local));
    bzero(&addr_peer, sizeof(addr_peer));

    int ret = ::getsockname(sockfd, reinterpret_cast<struct sockaddr*>(&addr_local), &slen);
    if (ret < 0) {
        CurrectThread::saveErrno(errno);
        LOG_ERROR << "SockOpt::isSelfConnect getsockname error, fd = " << sockfd;
        CurrectThread::saveErrno(0);
    }
    ret = ::getpeername(sockfd, reinterpret_cast<struct sockaddr*>(&addr_peer), &slen);
    if (ret < 0) {
        CurrectThread::saveErrno(errno);
        LOG_ERROR << "SockOpt::isSelfConnect getpeername error, fd = " << sockfd;
        CurrectThread::saveErrno(0);
    }
    return addr_local.sin_family == addr_peer.sin_family
            && addr_local.sin_addr.s_addr == addr_peer.sin_addr.s_addr
            && addr_local.sin_port == addr_peer.sin_port;
}

} // namespace net

} // namespace toym
